package delayqueue

import (
	"container/list"
	"time"
)

type Task struct {
	item  interface{}
	round int
}

type DelayQueue struct {
	slotNum    int
	slots      []*list.List
	taskChan   chan interface{}
	currentPos int
}

func NewDelayQueue() *DelayQueue {
	var slotNum = 3600
	var slots = make([]*list.List, slotNum)
	for i := 0; i < slotNum; i++ {
		slots[i] = list.New()
	}

	q := &DelayQueue{
		slots:    slots,
		slotNum:  slotNum,
		taskChan: make(chan interface{}),
	}

	go q.run()
	return q
}

func (q *DelayQueue) Enqueue(item interface{}, delay time.Duration) {
	round, pos := q.getPos(delay)
	task := &Task{
		item:  item,
		round: round,
	}
	q.slots[pos].PushBack(task)
}

func (q *DelayQueue) Chan() <-chan interface{} {
	return q.taskChan
}

func (q *DelayQueue) getPos(d time.Duration) (int, int) {
	round := int(d.Seconds()) / q.slotNum
	pos := (q.currentPos + int(d.Seconds())) % q.slotNum
	return round, pos
}

func (q *DelayQueue) run() {
	ticker := time.NewTicker(time.Second)
	for {
		select {
		case <-ticker.C:
			slot := q.slots[q.currentPos]
			var expires []*list.Element
			for e := slot.Front(); e != nil; e = e.Next() {
				task := e.Value.(*Task)
				if task.round > 0 {
					task.round--
					continue
				}
				expires = append(expires, e)
				q.taskChan <- e.Value.(*Task).item
			}
			for _, ex := range expires {
				slot.Remove(ex)
			}
			q.currentPos++
			q.currentPos = q.currentPos % q.slotNum
		}
	}
}
